package com.gitee.wsl.io

import com.gitee.wsl.ext.base.and
import com.gitee.wsl.ext.array.hashSHA256toString
import com.gitee.wsl.ext.array.hashSHA512toString
import com.gitee.wsl.security.hash.sha256
import com.gitee.wsl.security.hash.sha512
import kotlinx.io.Buffer
import kotlinx.io.EOFException
import kotlinx.io.UnsafeIoApi
import kotlinx.io.readByteArray
import kotlinx.io.readString
import kotlinx.io.unsafe.UnsafeBufferOperations
import kotlin.contracts.ExperimentalContracts
import kotlin.contracts.InvocationKind
import kotlin.contracts.contract
import kotlin.math.min

const val MAX_PREFIX_SIZE = 64L
const val CODE_POINT_SIZE = 16

val Buffer.writeRemaining: Int get() = Int.MAX_VALUE

/**
 * Returns true if the [Buffer] contains human readable text. Uses a small sample
 * of code points to detect unicode control characters commonly used in binary file signatures.
 */
val Buffer.isProbablyPlainText
    get() = try {
        val prefix = Buffer()
        prefix.readString()
        val byteCount = min(size, MAX_PREFIX_SIZE)
        copyTo(prefix, 0, byteCount)
        sequence<Int>{ while (!prefix.exhausted()) yield(prefix.readUtf8CodePoint()) }
            .take(CODE_POINT_SIZE)
            .all { codePoint -> codePoint.isPlainTextChar() }
    } catch (_: EOFException) {
        false // Truncated UTF-8 sequence
    }

internal const val REPLACEMENT_BYTE: Byte = '?'.code.toByte()
internal const val REPLACEMENT_CHARACTER: Char = '\ufffd'
internal const val REPLACEMENT_CODE_POINT: Int = REPLACEMENT_CHARACTER.code

internal fun Int.isPlainTextChar() = this.toChar().run { isWhitespace() || isISOControl() }
internal fun Buffer.readUtf8CodePoint(): Int = commonReadUtf8CodePoint()

@OptIn(ExperimentalStdlibApi::class)
internal fun Buffer.commonReadUtf8CodePoint(): Int {
    if (size == 0L) throw EOFException()

    val b0 = this[0]
    var codePoint: Int
    val byteCount: Int
    val min: Int

    when {
        (b0 and 0x80) == 0 -> {
            // 0xxxxxxx.
            codePoint = b0 and 0x7f
            byteCount = 1 // 7 bits (ASCII).
            min = 0x0
        }
        (b0 and 0xe0) == 0xc0 -> {
            // 0x110xxxxx
            codePoint = b0 and 0x1f
            byteCount = 2 // 11 bits (5 + 6).
            min = 0x80
        }
        (b0 and 0xf0) == 0xe0 -> {
            // 0x1110xxxx
            codePoint = b0 and 0x0f
            byteCount = 3 // 16 bits (4 + 6 + 6).
            min = 0x800
        }
        b0 and 0xf8 == 0xf0 -> {
            // 0x11110xxx
            codePoint = b0 and 0x07
            byteCount = 4 // 21 bits (3 + 6 + 6 + 6).
            min = 0x10000
        }
        else -> {
            // We expected the first byte of a code point but got something else.
            skip(1)
            return REPLACEMENT_CODE_POINT
        }
    }

    if (size < byteCount) {
        throw EOFException("size < $byteCount: $size (to read code point prefixed 0x${b0.toHexString()})")
    }

    // Read the continuation bytes. If we encounter a non-continuation byte, the sequence consumed
    // thus far is truncated and is decoded as the replacement character. That non-continuation byte
    // is left in the stream for processing by the next call to readUtf8CodePoint().
    for (i in 1 until byteCount) {
        val b = this[i.toLong()]
        if (b and 0xc0 == 0x80) {
            // 0x10xxxxxx
            codePoint = codePoint shl 6
            codePoint = codePoint or (b and 0x3f)
        } else {
            skip(i.toLong())
            return REPLACEMENT_CODE_POINT
        }
    }

    skip(byteCount.toLong())

    return when {
        codePoint > 0x10ffff -> {
            REPLACEMENT_CODE_POINT // Reject code points larger than the Unicode maximum.
        }
        codePoint in 0xd800..0xdfff -> {
            REPLACEMENT_CODE_POINT // Reject partial surrogates.
        }
        codePoint < min -> {
            REPLACEMENT_CODE_POINT // Reject overlong code points.
        }
        else -> codePoint
    }
}

internal fun Buffer.writeByte(value:Int) = writeByte(value.toByte())

fun Buffer.canRead(): Boolean = !exhausted()

/**
 * Read the specified number of bytes specified (optional, read all remaining by default)
 */
 fun Buffer.readBytes(count: Int = size.toInt()): ByteArray {
    return readByteArray(count)
}

fun Buffer.isEmpty(): Boolean = size == 0L

/*
internal fun Buffer.commonWriteUtf8CodePoint(codePoint: Int): Buffer {
    when {
        codePoint < 0x80 -> {
            // Emit a 7-bit code point with 1 byte.
            writeByte(codePoint)
        }
        codePoint < 0x800 -> {
            // Emit a 11-bit code point with 2 bytes.
            val tail = writableSegment(2)
            */
/* ktlint-disable no-multi-spaces *//*

            tail.data[tail.limit    ] = (codePoint shr 6          or 0xc0).toByte() // 110xxxxx
            tail.data[tail.limit + 1] = (codePoint       and 0x3f or 0x80).toByte() // 10xxxxxx
            */
/* ktlint-enable no-multi-spaces *//*

            tail.limit += 2
            size += 2L
        }
        codePoint in 0xd800..0xdfff -> {
            // Emit a replacement character for a partial surrogate.
            writeByte('?'.code)
        }
        codePoint < 0x10000 -> {
            // Emit a 16-bit code point with 3 bytes.
            val tail = writableSegment(3)
            */
/* ktlint-disable no-multi-spaces *//*

            tail.data[tail.limit    ] = (codePoint shr 12          or 0xe0).toByte() // 1110xxxx
            tail.data[tail.limit + 1] = (codePoint shr  6 and 0x3f or 0x80).toByte() // 10xxxxxx
            tail.data[tail.limit + 2] = (codePoint        and 0x3f or 0x80).toByte() // 10xxxxxx
            */
/* ktlint-enable no-multi-spaces *//*

            tail.limit += 3
            size += 3L
        }
        codePoint <= 0x10ffff -> {
            // Emit a 21-bit code point with 4 bytes.
            val tail = writableSegment(4)
            */
/* ktlint-disable no-multi-spaces *//*

            tail.data[tail.limit    ] = (codePoint shr 18          or 0xf0).toByte() // 11110xxx
            tail.data[tail.limit + 1] = (codePoint shr 12 and 0x3f or 0x80).toByte() // 10xxxxxx
            tail.data[tail.limit + 2] = (codePoint shr  6 and 0x3f or 0x80).toByte() // 10xxyyyy
            tail.data[tail.limit + 3] = (codePoint        and 0x3f or 0x80).toByte() // 10yyyyyy
            */
/* ktlint-enable no-multi-spaces *//*

            tail.limit += 4
            size += 4L
        }
        else -> {
            throw IllegalArgumentException("Unexpected code point: 0x${codePoint.toHexString()}")
        }
    }

    return this
}*/


fun Buffer.sha256ToString() = readBytes().hashSHA256toString()

fun Buffer.sha512ToString() = readBytes().hashSHA512toString()

fun Buffer.sha512() = readBytes().sha512()

fun Buffer.sha256() = readBytes().sha256()

@OptIn(UnsafeIoApi::class)
fun Buffer.readStringImpl(byteCount: Long, charset: Charset): String {
    require(byteCount >= 0 && byteCount <= Int.MAX_VALUE) {
        "byteCount ($byteCount) is not within the range [0..${Int.MAX_VALUE})"
    }
    if (size < byteCount) {
        throw EOFException("Buffer contains less bytes then required (byteCount: $byteCount, size: $size)")
    }
    if (byteCount == 0L) return ""

    var result: String? = null
    UnsafeBufferOperations.readFromHead(this) { data, pos, limit ->
        val len = limit - pos
        if (len >= byteCount) {
            result = data.decodeToString(charset, pos, pos + byteCount.toInt())
            byteCount.toInt()
        } else {
            0
        }
    }
    return result ?: readByteArray(byteCount.toInt()).decodeToString(charset)
}


@OptIn(ExperimentalContracts::class)
inline fun <T> Buffer.writeOptional(value: T?, writer: (T, Buffer) -> Unit) {
    contract {
        callsInPlace(writer, InvocationKind.AT_MOST_ONCE)
    }
    writeByte(if (value != null) 1 else 0)
    value?.let { writer(it, this) }
}

@OptIn(ExperimentalContracts::class)
inline fun <T> Buffer.readOptional(reader: (Buffer) -> T): T? {
    contract {
        callsInPlace(reader, InvocationKind.AT_MOST_ONCE)
    }
    return if (readByte() == 0.toByte()) null
    else reader(this)
}


inline fun <T> Buffer.writeList(values: List<T>, writer: (T, Buffer) -> Unit) {
    writeInt(values.size)
    values.forEach { writer(it, this) }
}

inline fun <T> Buffer.readList(reader: (Buffer) -> T): List<T> {
    return (0..<readInt()).map { reader(this) }
}